package com.yishuifengxiao.common.crawler.content;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import org.apache.http.HttpStatus;

import com.yishuifengxiao.common.crawler.content.impl.SimpleContentExtract;
import com.yishuifengxiao.common.crawler.content.matcher.ContentMatcher;
import com.yishuifengxiao.common.crawler.content.matcher.SimpleContentMatcher;
import com.yishuifengxiao.common.crawler.domain.entity.Page;
import com.yishuifengxiao.common.crawler.domain.model.ContentRule;
import com.yishuifengxiao.common.crawler.domain.model.ExtractRule;
import com.yishuifengxiao.common.crawler.extractor.ExtractorFactory;
import com.yishuifengxiao.common.crawler.extractor.content.ContentExtractor;
import com.yishuifengxiao.common.crawler.extractor.content.impl.CharsetContentExtractor;
import com.yishuifengxiao.common.crawler.extractor.content.impl.DescpContentExtractor;
import com.yishuifengxiao.common.crawler.extractor.content.impl.KeywordContentExtractor;
import com.yishuifengxiao.common.crawler.extractor.content.impl.TitleContentExtractor;
import com.yishuifengxiao.common.tool.exception.ServiceException;

import lombok.extern.slf4j.Slf4j;

/**
 * 内容解析器装饰器<br/>
 * 进行内容解析前的前置操作<br/>
 * 功能如下：<br/>
 * 1. 决定是否对该网页进行内容提取<br/>
 * 2. 调用真正的内容解析器进行内容解析
 * 
 * @author yishui
 * @date 2019年11月26日
 * @version 1.0.0
 */
@Slf4j
public class ContentExtractDecorator implements ContentExtract {
	/**
	 * 提取器生成工厂
	 */
	private final ExtractorFactory factory = new ExtractorFactory();

	/**
	 * 内容匹配器
	 */
	protected ContentMatcher contentMatcher = new SimpleContentMatcher();

	/**
	 * 根据风铃虫内容解析规则创建的内置内容解析器
	 */
	protected ContentExtract simpleContentExtract;

	/**
	 * 用户自定义的内容解析器
	 */
	protected ContentExtract contentExtract;

	@Override
	public void extract(final ContentRule contentRule, final List<ExtractRule> rules, final Page page)
			throws ServiceException {
		if (null == page) {
			return;
		}

		if (HttpStatus.SC_OK != page.getCode()) {
			page.setSkip(true);
			log.debug("Page {} has a response code of {} and will not extract data from it", page.getRequest().getUrl(),
					page.getCode());
			return;
		}

		// 根据请求的页面的地址判断是否符合内容页规则
		boolean match = this.contentMatcher.match(contentRule.getContentPageRule(), page.getRequest().getUrl());

		if (match && null != page.getRedirectUrl()) {
			// 根据请求的页面的地址判断是否符合内容页规则
			match = this.contentMatcher.match(contentRule.getContentPageRule(), page.getRedirectUrl());
		}

		if (match) {
			// 根据请求的页面的内容判断是否符合内容页规则
			match = this.contentMatcher.match(contentRule.getMatcher(), page.getRawTxt());
		}

		log.debug("Whether the web page [{}] matches the content page parsing rule is {}", page.getRequest().getUrl(),
				match);

		page.setSkip(!match);

		if (match) {
			// 开始真正的内容解析操作
			this.getSimpleContentExtract(rules).extract(contentRule, rules, page);
			// 调用用户自定义内容解析器
			if (null != this.contentExtract) {
				this.contentExtract.extract(contentRule, rules, page);
			}
		}

	}

	/**
	 * 获取系统默认的内容解析器
	 * 
	 * @param rules 内容提取规则
	 * @return 系统默认的内容解析器
	 */
	private ContentExtract getSimpleContentExtract(final List<ExtractRule> rules) {
		if (null == this.simpleContentExtract) {
			this.simpleContentExtract = new SimpleContentExtract(this.createContentExtractors(rules));
		}
		return this.simpleContentExtract;
	}

	/**
	 * 生成所有的内容提取器
	 * 
	 * @param rules 内容提取规则
	 * @return 所有的内容提取器
	 */
	private List<ContentExtractor> createContentExtractors(final List<ExtractRule> rules) {
		// 根据内容解析规则获取到所有的内容抽取器
		List<ContentExtractor> contentExtractors = this.buildContentExtractor(rules);

		// 添加系统内置的抽取器
		contentExtractors.addAll(Arrays.asList(new DescpContentExtractor(), new KeywordContentExtractor(),
				new TitleContentExtractor(), new CharsetContentExtractor()));
		// 构建一个内容解析装饰器
		return contentExtractors;
	}

	/**
	 * 根据内容解析规则构建所有的内容提取器
	 * 
	 * @param extractRules 所有的提取规则
	 * @return 内容提取器集合
	 */
	private List<ContentExtractor> buildContentExtractor(List<ExtractRule> extractRules) {

		List<ContentExtractor> contentExtractors = new ArrayList<>();

		extractRules.stream().map(factory::getContentExtractor).forEach(contentExtractors::add);

		return contentExtractors.stream().filter(Objects::nonNull).collect(Collectors.toList());
	}

	public ContentExtractDecorator(ContentExtract contentExtract) {
		this.contentExtract = contentExtract;

	}

}
